﻿using System;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Channels;
using Microsoft.ServiceBus;
using SimpleServiceBus.Bus;
using SimpleServiceBus.Bus.Transport;
using SimpleServiceBus.Endpoints.DotNetServiceBus.Utilities;

namespace SimpleServiceBus.Endpoints.DotNetServiceBus.Transport
{
    public class DotNetServiceBusTransport : BaseTransport
    {
        public const string SsbHeaderNamespace = "http://www.simpleservicebus.com/wcf/headers/";
        public const string DestinationBusUserNameHeaderKey = "DotNetServiceBusUserName";
        public const string DestinationBusPasswordHeaderKey = "DotNetServiceBusPassword";

        private AzureQueueManager _inputQueue;
        private Uri _queueUri;


        public string InputQueueName { get; set; }
        public string PoisonQueueName { get; set; }
        public string BusSolutionName { get; set; }
        public string BusSolutionPassword { get; set; }
        public TimeSpan ReceiveTimeOut { get; set; }
        public TimeSpan SendTimeOut { get; set; }

        public DotNetServiceBusTransport()
        {
            InputQueueName = Dns.GetHostName();
            PoisonQueueName = InputQueueName + "_Poison";
            ReceiveTimeOut = TimeSpan.FromSeconds(30);
            SendTimeOut = TimeSpan.FromSeconds(30);
        }

        public Uri QueueUri
        {
            get
            {
                if (_queueUri == null)
                {
                    _queueUri = ServiceBusEnvironment.CreateServiceUri("sb", BusSolutionName, "/" + InputQueueName + "/");
                    
                }
                return _queueUri;
            }
            set { _queueUri = value; }
        }


        public override string ReturnAddress
        {
            get
            {
                return QueueUri.ToString();
            }
        }

        public override void Send(MessageEnvelope m, params string[] destinations)
        {
            foreach (string destination in destinations)
            {
                 Message message = CreateWcfMessageFromMessageEnvelope(m);
                 ChannelFactory<IOnewayReceiver> target = GetDestinationChannelFactoryFor(m, destination);
                try
                {
                    target.CreateChannel().Send(message);
                    target.Close();
                }
                catch (Exception)
                {
                    target.Abort();
                    throw;
                } 
            }
        }

        public override int GetNumberOfPendingMessages()
        {
            return -1;
        }

        protected override MessageEnvelope GetNextMessage()
        {
            MessageEnvelope envelope;
            Message message = null;
            try
            {
                message = _inputQueue.Queue.PeekLock(ReceiveTimeOut);
                envelope = CreateMessageEnvelopeFromWcfMessage(message);
            }
            catch (Exception)
            {
                if (message != null)
                    _inputQueue.Queue.ReleaseLock(message);
                throw;
            }
            _inputQueue.Queue.DeleteLockedMessage(message);
            return envelope;
        }

        protected override void StartService()
        {
            BindToInputQueue();
            base.StartService();
        }

        protected override void ShutDownService()
        {
            if (_inputQueue != null)
                _inputQueue.Dispose();
            base.ShutDownService();
        }

        protected void BindToInputQueue()
        {
            _inputQueue = new AzureQueueManager(QueueUri,BusSolutionName,BusSolutionPassword);

        }

        protected virtual MessageEnvelope CreateMessageEnvelopeFromWcfMessage(Message wcfMessage)
        {
            MessageEnvelope result = CreateEmptyMessageEnvelope();
            if (wcfMessage.Headers != null && wcfMessage.Headers.Count > 0)
            {
                for (int i=0;i<wcfMessage.Headers.Count;i++)
                {
                    var info = wcfMessage.Headers[i];
                    if (info.Namespace == SsbHeaderNamespace)
                        result.Headers.Add(info.Name, wcfMessage.Headers.GetHeader<string>(info.Name, info.Namespace));
                }
            }

            result.Payload = wcfMessage.GetBody<byte[]>();
            return result;
        }

        protected virtual Message CreateWcfMessageFromMessageEnvelope(MessageEnvelope envelope)
        {
            Message message = Message.CreateMessage(MessageVersion.Default, "Send", envelope.Payload);
            foreach (string headerKey in envelope.Headers)
            {
                message.Headers.Add(MessageHeader.CreateHeader(headerKey,SsbHeaderNamespace,envelope.Headers[headerKey]));
            }
            message.Headers.From = new EndpointAddress(QueueUri);
            message.Headers.ReplyTo = new EndpointAddress(QueueUri);
            return message;
        }

        private static ChannelFactory<IOnewayReceiver> GetDestinationChannelFactoryFor(IHeadersProvider message,string destinationAddress)
        {
           
            var channelFactory =
                  new ChannelFactory<IOnewayReceiver>(
                      new NetOnewayRelayBinding(EndToEndSecurityMode.Transport,
                                                RelayClientAuthenticationType.RelayAccessToken),
                      new EndpointAddress(destinationAddress));

            string userName = message.Headers[DestinationBusUserNameHeaderKey];
            if (!String.IsNullOrEmpty(userName))
            {
                string password = message.Headers[DestinationBusPasswordHeaderKey];
                if (!String.IsNullOrEmpty(password))
                {
                    var credential = AzureQueueManager.CreateUsernamePasswordCredential(userName, password);
                    channelFactory.Endpoint.Behaviors.Add(credential);
                }
            }
            return channelFactory;
        }
    }
}
